import fs from "fs/promises";
import path from "path";
import { BaseListChatMessageHistory } from "@langchain/core/chat_history";
import { HumanMessage, AIMessage } from "@langchain/core/messages";

export class FileChatMessageHistory extends BaseListChatMessageHistory {
  /**
   *
   * @param {*} filepath 要写入的文件地址
   */
  constructor(filepath) {
    super();
    this.filepath = filepath; // 写入的文件路径
    this._q = Promise.resolve(); // 串行队列的初始状态
  }

  // 串行队列
  // fn 就是一个任务，_enqueue 就是将该任务放置到队列里面
  // 作用：防止同时读写文件导致数据不一致
  _enqueue(fn) {
    const run = () => fn();
    const p = this._q.then(run, run);
    this._q = p.catch(() => {}); // 保持队列不中断
    return p;
  }

  // 确保目录存在
  async _ensureDir() {
    await fs.mkdir(path.dirname(this.filepath), {
      recursive: true,
    });
  }

  /**
   * 原子写入
   * @param {*} text 要写入的文本
   */
  async _atomicWrite(text) {
    await this._ensureDir();
    const tmp =
      this.filepath +
      `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`; // 临时的文件目录地址
    await fs.writeFile(tmp, text, "utf8"); // 先写入到临时文件
    await fs.rename(tmp, this.filepath); // 进行原子替换
  }

  // 读取原始数据
  async _readRaw() {
    // "[{type: 'human', content: 'xxxx'},{type: 'ai', content: 'xxxx}]"
    try {
      const txt = await fs.readFile(this.filepath, "utf8");
      const arr = JSON.parse(txt);
      return Array.isArray(arr) ? arr : [];
    } catch (e) {
      if (e && e.code === "ENOENT") return [];
      if (e instanceof SyntaxError) {
        console.warn(`历史文件损坏，已忽略：${this.filepath}`);
        return [];
      }
      throw e;
    }
  }

  // 保存消息
  /**
   *
   * @param {*} messages 消息对象数组
   */
  async _saveMessages(messages) {
    const raw = messages.map((m) => ({
      type: m.getType(),
      content: m.content,
    }));
    await this._atomicWrite(JSON.stringify(raw, null, 2));
  }

  // 下面的方法就是对外的接口

  async getMessages() {
    const raw = await this._readRaw(); // 从文件中进行读取
    return raw.map((r) =>
      r.type === "human"
        ? new HumanMessage(r.content)
        : new AIMessage(r.content)
    );
  }

  /**
   * 添加一条消息
   * @param {*} message 消息对象
   */
  async addMessage(message) {
    return this._enqueue(async () => {
      const msgs = await this.getMessages();
      msgs.push(message);
      await this._saveMessages(msgs);
    });
  }

  async addMessages(messages) {
    return this._enqueue(async () => {
      const msgs = await this.getMessages();
      msgs.push(...messages);
      await this._saveMessages(msgs);
    });
  }

  async clear() {
    return this._enqueue(async () => {
      this._atomicWrite("[]");
    });
  }
}
